// Prototypes

// Convert a string into an XML object
String.prototype.toXML = function() {
    var xml;
    try {
        xml = new XML(this)
    } catch(e) {
        throw('Cannot convert to XML: ' + this);
    }
    return xml;
}

// NOTEX JavaScript library

var notex = {

    // Get the app and token parameters
    app   : config('app'),
    token : config('token'),

    // Return a query string with the token
    query : function(is_PUT_or_POST) {
        var q = (is_PUT_or_POST ? '' : '?');
        return q + 'token=' + this.token;
    },

    // Describe how the script is used
    __use : <usage/>,
    usage : function(desc, params) {
        var use = new XML();
        for (var p in params) {
            use += <param desc={ params[p] }>{ p }</param>
        }
        if (params) use = <params>{ use }</params>
        if (desc) {
            use += <description>{ desc }</description>
            this.__use = <usage>{ use }</usage>
        }
        return this.__use;
    },

    // Throw an error message with usage
    error : function(message) {
        var error = <message>{ message }</message>
        error += this.usage();
        throw(error.toXMLString());
    }
};

notex.config = {
    database_server : 'mysql.php', // database adaptor to use for SQL services
    auth_cache_secs : 600,         // secs to cache the auth services data for
    session_secs    : 600          // secs to save a session during inactivity
};

notex.session = {
    id : param('session_id') || cookie('notex_session_id'),
    ip : param('remote_addr') || env('REMOTE_ADDR'),
    user_id : 0,
    xml : null,

    // Create a new user session
    create : function(user_id) {
        var id = md5(this.ip + Math.random());
        this.id = id; cookie('notex_session_id', id);
        this.user_id = user_id;
        return this.saveData();
    },

    // Load our session
    load : function() {
        var sessions = load(this.ip).toXML();
        if (sessions && this.id) {
            this.xml = sessions.session.(@id == this.id);
            this.user_id = this.xml.user.id.toString();
        } else {
            this.xml = null;
        }
        return this.xml;
    },

    // Save session data as XML
    saveData : function(data) {
        if (!this.id || !this.user_id) return;
        var time = new Date().getTime();
        data = data || '';
        this.xml = <session id={ this.id }><user><id>{ this.user_id }</id></user><time>{ time }</time><data>{ data }</data></session>
        var contents = load(this.ip);
        var sessions = contents.toXML();
        if (contents) {
            var found = sessions.session.(@id == this.id)
            if (found.time != undefined) {
                found.time = <time>{ time }</time>;
                found.data = <data>{ data }</data>;
            } else {
                sessions.appendChild(this.xml);
            }
        } else {
            sessions = <sessions>{ this.xml }</sessions>
        }
        save(this.ip, sessions.toXMLString());
        return this.xml;
    }
};

notex.service = {
    base_url : '/apps/notex/services/',

    // Check whether a session is authorized to use a service name
    auth : function(service_name) {

        // Get the service list as XML
        var url = this.base_url + 'security/auth.php';
        var params = 'session_id=' + notex.session.id;
        params += '&remote_addr=' + notex.session.ip;
        var xml = notex.session.xml;
        if (!xml) {
            xml = notex.session.load();
            if (xml.data.auth.length() == 0) {
                var data = POST(url, params).toXML();
                xml = notex.session.saveData(data);
            }
        }

        // Match an authorized service
        var match;
        try {
            match = xml.data.auth.apps.app.( @name == notex.app && @token == notex.token ).services.service.( @name == service_name );
        } catch(e) {}

        // Throw an error if we failed
        if (!match || match.length() == 0)
            notex.error('User session is not authorized to use service "' + service_name + '" in app "' + notex.app + '"');
    },

    // Return the XML result of running some SQL on the database service
    sql : function(sql, options) {
        if (!options) options = {};
        var db_user = options.db_user || env('DB_USER') || notex.app;
        var db_pass = options.db_pass || env('DB_PASS');
        var db_host = options.db_host || env('DB_HOST');
        var database = options.database || env('DATABASE') || notex.app;
        var dsn = 'database/' + notex.config.database_server;
        var out = POST(this.base_url + dsn,     'db_user=' + db_user +
                       '&db_pass=' + db_pass + '&db_host=' + db_host +
                       '&database=' + database + '&sql=' + escape(sql));
        return out.toXML();
    },

    // Return the cached XML result of running some SQL on the database service
    sql_cached : function(database, sql, secs) {
        var cache = load(database + sql, secs);
        if (cache) return new XML(cache);
        var xml = this.sql(database, sql);
        save(database + sql, xml.toXMLString());
        return xml;
    }
};

// NOTEX Object Relational Mapping service

notex.service.orm = {

    // Perform an ORM action on a table with an object
    action : function(action, table, object, options) {
        var method = this[action];
        if (!method) notex.error('No ORM action "' + action + '"');
        return method.call(this, table, object, options);
    },

    // Insert a new object into a database table
    insert : function(table, object, options) {
        var fields = '';
        var values = '';
        for (field in object) {
            if (fields) fields += ','; fields += field;
            if (values) values += ','; values += '"' + object[field] + '"';
        }
        var sql = 'insert into ' + table + '(' + fields + ') values (' + values + ')';
        return notex.service.sql(sql, options);
    },

    // Update an object in a database table
    update : function(table, object, options) {
        if (!object.id) notex.error('No object ID');
        var sql = 'update ' + table + ' set ' + this.__clause(object, ',');
        sql += ' where id=' + object.id;
        return notex.service.sql(sql, options);
    },

    // Delete some objects from a database table
    delete : function(table, object, options) {
        var sql = 'delete from ' + table + ' where ' + this.__clause(object);
        return notex.service.sql(sql, options);
    },

    // Select a list of objects from a database table
    select : function(table, object, options) {
        var sql = 'select * from ' + table + ' where ' + this.__clause(object);
        return notex.service.sql(sql, options);
    },

    // Turn an object into a where clause
    __clause : function(object, sep) {
        if (!sep) sep = ' and ';
        if (!object) return 'true';
        var clause = '';
        for (field in object) {
            if (clause) clause += sep;
            clause += field + '="' + object[field]+ '"';
        }
        return clause;
    }
};

// END
